{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 问题：Scrapy 和 scrapy-redis 有什么区别？为什么选择 redis 数据库？\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "面试官想从这道题中考察面试人什么？\n",
    "这个问题的关键是后面部分，面试官可能会把问题的重点放在为什么是 redis？比如，为什么不是scrapy-mongo，或其它的。\n",
    "\n",
    "答案\n",
    "scrapy 是一个 Python 爬虫框架，爬取效率极高，具有高度定制性，但是不支持分布式。而scrapy-redis 一套基于 redis 数据库、运行在 scrapy 框架之上的组件，可以让 scrapy 支持分布式策略，Slaver 端共享 Master 端 redis 数据库里的 item 队列、请求队列和请求指纹集合；\n",
    "为什么选择 redis 数据库，因为 redis 支持主从同步，而且数据都是缓存在内存中的，所以基于redis 的分布式爬虫，对请求和数据的高频读取效率非常高。\n",
    "拓展\n",
    "Scrapy 是一个爬虫框架，scrapy-redis 则是一个基于 Redis 的 Scrapy 分布式组件，它可以让爬虫跑的更快。\n",
    "\n",
    "你们知道Redis是什么吗？它适合做什么呢？\n",
    "\n",
    "Redis数据库是一种基于键值对的NoSQL数据库，它将所有数据都存放在内存中，而且可以将内存的数据以快照和日志的形式保存在硬盘中，这样一些意外的断电或机器故障也不会导致数据丢失。\n",
    "\n",
    "Redis的特性有哪些呢？\n",
    "\n",
    "速度快\n",
    "提供了5种数据结构的键值对数据库\n",
    "发布订阅功能，实现消息系统\n",
    "键过期功能，用于缓存\n",
    "简单的事务功能\n",
    "使用单线程模型，简单稳定\n",
    "主从复制\n",
    "高可用和分布式\n",
    "我们知道了 Redis 的优势以后，看看它是怎么和Scrapy结合起来的？\n",
    "\n",
    "由于Scrapy不支持分布式的，因为它的调度器和去重全部是在机器内存中实现的。而scrapy-redis就直接让Redis替换机器内存，将调度器和去重的工作全部让Redis来实现。它利用Redis对用于爬取的请求(Request)进行存储和调度(Schedule)，并对爬取产生的项目(items)存储以供后续处理使用。scrapy-redis重写了scrapy一些比较关键的代码，将scrapy变成一个可以在多个主机上同时运行的分布式爬虫。\n",
    "\n",
    "scrapy-redis可以让多个爬虫在各自的主机上去访问同一个Redis来判断这次的request请求是否是重复的了，不同的爬虫分工合作，这样速度快，也不会重复爬取相同的网页。并且，scrapy-redis依旧使用url的fingerprint来判断request请求是否已经出现过，每个爬过的对象也会有一个唯一的指纹存在redis中，那么每一个request在进入调度之前都要进行指纹判重，如果重复就不需要参加调度。总而言之，每个主机上的爬虫进程都访问同一个redis数据库，所以调度和判重都统一进行统一管理，达到了分布式爬虫的目的。\n",
    "\n",
    "图片描述\n",
    "\n",
    "scrapy-redis所实现的两种分布式：爬虫分布式以及item处理分布式。\n",
    "\n",
    "从上一道面试题中，我们知道Scrapy在爬取过程当中有一个主要的数据结构是“待爬队列”，以及能够操作这个队列的调度器（也就是Scheduler）。如果你去读Scrapy的源代码，你会发现这个“待爬队列”的数据结构就是使用Python自带的collections.deque改造过的一种优先级队列。那么，如何支持多个爬虫共同使用这个队列呢？scrapy-redis提供了一个解决方法，把deque换成redis数据库，我们从同一个redis服务器存放要爬取的request，这样就能让多个爬虫去同一个数据库里读取。它把待爬队列按照优先级建立了一个字典结构，然后根据request中的优先级，来决定该入哪个队列，出列时则按优先级较小的优先出列。为了管理这个比较高级的队列字典，Scheduler需要提供一系列的方法。但是原来的Scheduler已经无法使用，所以使用Scrapy-redis的Scheduler组件。\n",
    "\n",
    "另外，Scrapy-redis还保留了Scrapy的request去重功能，redis中有一个指纹集合，scrapy-redis调度器从引擎接受request，将request的指纹存入redis的set检查是否重复，并将不重复的request push写入redis的request queue。Spider发出的引擎请求request时，调度器从redis的request queue队列里根据优先级pop出个request返回给引擎，引擎将此request发给爬虫处理。如果调度器发现这个spider对应的redis的调度器队列空了，就会触发信号spider_idle，spider收到这个信号之后，直接连接redis读取strart_url池，去拿新的一批url入口，然后再次重复上边的工作。\n",
    "\n",
    "scrapy-redis 的Item Pipeline将爬取到的Item 存入redis的 items queue。修改过Item Pipeline可以很方便的根据key从items queue提取item，从而实现 items processes集群。\n",
    "\n",
    "其实scrapy-redis的源码并不是很多，大家有兴趣的可以clone下来看看，我认为读代码比看文档要更直观一点。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
